package com.example.service;

import com.example.model.CargoRequest;
import com.example.model.DeckRequest;
import com.example.model.ReponseResult;
import com.example.utils.CargoColorUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
@Slf4j
@Component
public class Compute implements LoadingService {

    private static final int SAFE_ZONE_WIDTH = 60;     // cm
    private static final int ITEM_SPACING    = 10;     // 默认间距
    private static final double MIN_ZONE_RATIO = 0.15; // 任何分区宽度 ≥ 甲板长×15%
    private List<String> companyOrder = new ArrayList<>();

    /* ─────────── 统一入口 ─────────── */
    @Override
    public ReponseResult computePlan(DeckRequest deck, List<CargoRequest> cargos) {

        int deckLen = deck.getDeckLength();      // 甲板长度
        int deckWid = deck.getDeckWidth();       // 甲板宽度
        int halfH   = (deckWid - SAFE_ZONE_WIDTH) / 2;   // 半甲板高度
        log.info("甲板长度：{} cm, 甲板宽度：{} cm, 甲板高度：{} cm", deckLen, deckWid, halfH);
        // ── A. 先确定公司顺序（保持第一次出现的顺序）──
       this.companyOrder = cargos.stream()
                .map(CargoRequest::getCompanyCode)
                .distinct()
                .collect(Collectors.toList());     // [A] 或 [A,B]

        // 按公司分组
        Map<String, List<CargoRequest>> byCompany =
                cargos.stream().collect(Collectors.groupingBy(CargoRequest::getCompanyCode));
        log.info("公司顺序：{}", String.join(",", this.companyOrder));
        List<ReponseResult.CargoPlanItem> planItems = new ArrayList<>();

        if (byCompany.size() == 1) {
            // 单家公司：整艘船共用一套“上下层”分区
            arrangeSingleCompany(
                    byCompany.values().iterator().next(),
                    0,                        // 下层起点
                    deckLen,
                    halfH,
                    planItems
            );

        } else if (byCompany.size() == 2) {
            /* ---------- 双家公司：上甲板 = 第一家公司，下甲板 = 第二家公司 ---------- */
            Iterator<Map.Entry<String, List<CargoRequest>>> it = byCompany.entrySet().iterator();
            Map.Entry<String, List<CargoRequest>> compA = it.next();   // 上
            Map.Entry<String, List<CargoRequest>> compB = it.next();   // 下

            // 1) 先各自布局本公司半甲板
            Set<Integer> placedA = arrangeHalfDeck(
                    compA.getValue(), true,
                    deckLen, halfH,
                    SAFE_ZONE_WIDTH + halfH,      // 上层 y-offset
                    planItems);

            Set<Integer> placedB = arrangeHalfDeck(
                    compB.getValue(), false,
                    deckLen, halfH,
                    0,                             // 下层 y-offset
                    planItems);

            /* 2) 跨公司回填：把未装载的普通/重货尝试塞进另一家公司空隙 */
            crossFill(compA.getValue(), placedA, deckLen, halfH,
                    SAFE_ZONE_WIDTH + halfH, planItems);  // A 剩余 → B 的空区
            crossFill(compB.getValue(), placedB, deckLen, halfH,
                    0,                      planItems);    // B 剩余 → A 的空区
        } else {
            throw new IllegalArgumentException("仅支持 1 或 2 家公司数据");
        }

        /* ───────── 统计信息 ───────── */
        int totalWeight = cargos.stream().mapToInt(CargoRequest::getVehicleWeight).sum();
        int deckArea    = deckLen * deckWid;
        int usedArea    = planItems.stream()
                .mapToInt(p -> p.getCargoLength() * p.getCargoWidth())
                .sum();
        int stowageRate = (int) Math.round(100.0 * usedArea / deckArea);

        return new ReponseResult(
                deck.getShipName(),
                deckWid,
                deckLen,
                planItems,
                totalWeight,
                stowageRate,
                "装载完成"
        );
    }

    /* ============================================================
                           单家公司完整装载
       ============================================================ */
    private void arrangeSingleCompany(
            List<CargoRequest> cargoList,
            int baseY,
            int deckLen,
            int halfH,
            List<ReponseResult.CargoPlanItem> out
    ) {
        List<Zone> zones = planZonesSingle(cargoList, deckLen, halfH * 2); // total_h = 2×halfH
        execZones(zones, baseY, halfH, out);
    }

    /* ============================================================
                           半甲板布局（双公司）
       ============================================================ */
    private Set<Integer> arrangeHalfDeck(
            List<CargoRequest> cargoList,
            boolean isUpper,
            int deckLen,
            int halfH,
            int baseY,
            List<ReponseResult.CargoPlanItem> out
    ) {
        List<Zone> zones = planZonesTwo(cargoList, deckLen, halfH * 2, isUpper);
        return execZones(zones, baseY, halfH, out);
    }

    /* ============================================================
                    共同：执行一个 zones 列表的摆放
       ============================================================ */
    public Set<Integer> execZones(
            List<Zone> zones,
            int baseY,
            int halfH,
            List<ReponseResult.CargoPlanItem> out
    ) {
        Set<Integer> placedIdSet = new HashSet<>();
        Map<String, ZoneState> zoneStates = new HashMap<>();

        /* 1. 先各分区均衡放置（上/下层） */
        for (Zone z : zones) {
            ZoneState st = new ZoneState(z.width, z.fromRight);
            st.absZoneX = z.startX;
            balancedPack(z.cargoList, st, halfH, placedIdSet);
            zoneStates.put(z.code, st);

            // 把已放 cargo 转为 planItem
            for (PlacedCargo pc : st.top) {
                out.add(toPlanItem(pc, z.startX + pc.x,
                        baseY + halfH + pc.y));           // top layer
            }
            for (PlacedCargo pc : st.bot) {
                out.add(toPlanItem(pc, z.startX + pc.x,
                        baseY + pc.y));                   // bot layer
            }
        }

        /* 2. 全局回填（跳过 SPX） */
        globalRefill(zoneStates, halfH, placedIdSet, true);

        /* 3. 把回填后新增的 cargo 也输出 */
        zoneStates.values().forEach(st -> {
            for (PlacedCargo pc : st.refillAdded) {
                int globalY = baseY + (pc.layer == Layer.TOP ? halfH : 0) + pc.y;
                out.add(toPlanItem(pc, pc.absZoneX + pc.x, globalY));
            }
        });
        return placedIdSet;
    }

    /* ============================================================
                           规划分区 (单家公司)
       ============================================================ */
    private List<Zone> planZonesSingle(List<CargoRequest> list,
                                       int deckLen,
                                       int totalH) {

        /* --- 预分组 --- */
        Map<String, List<CargoRequest>> g =
                list.stream().collect(Collectors.groupingBy(CargoRequest::getCargoCode));
        List<CargoRequest> dfGC  = g.getOrDefault("GC",  Collections.emptyList());
        List<CargoRequest> dfWXP = g.getOrDefault("WXP", Collections.emptyList());
        List<CargoRequest> dfSPX = g.getOrDefault("SPX", Collections.emptyList());
        List<CargoRequest> dfZH  = g.getOrDefault("ZH",  Collections.emptyList());

        List<CargoRequest> dfPT  = list.stream()
                .filter(c -> !Set.of("GC","WXP","SPX","ZH").contains(c.getCargoCode()))
                .collect(Collectors.toList());

        int lenGC  = dfGC.stream().mapToInt(CargoRequest::getCargoLength).max().orElse(0);

        int areaWXP = dfWXP.stream()
                .mapToInt(c -> c.getCargoLength() * c.getCargoWidth()).sum();
        int lenWXP  = (int) Math.ceil(areaWXP / (double) totalH);

        /* ---------- 计算 SPX 分区长度（网格法） ---------- */
        int lenSPX;
        if (!dfSPX.isEmpty()) {
            int halfH = totalH / 2;
            int maxH  = dfSPX.stream().mapToInt(CargoRequest::getCargoWidth).max().orElse(0);
            int maxW  = dfSPX.stream().mapToInt(CargoRequest::getCargoLength).max().orElse(0);
            int nSPX  = dfSPX.size();

            int rows  = (maxH > 0) ? halfH / maxH : 0;
            int cols  = rows > 0 ? (int) Math.ceil(nSPX / (rows * 2.0)) : 0;
            lenSPX    = cols > 0 ? cols * maxW + ITEM_SPACING : 0;
        } else {
            lenSPX = 0;
        }

        int preLen = lenGC + lenWXP + lenSPX;
        int lenZH  = Math.max((deckLen - preLen) / 2, 0);
        int lenPT  = Math.max(deckLen - (preLen + lenZH), 0);

        /* ---------- 生成顺序 & fromRight ---------- */
        List<Zone> zones = new ArrayList<>();
        final int[] cur = {0};
        BiConsumer<String, List<CargoRequest>> add = (code, lst) -> {
            if (lst == null || lst.isEmpty()) return;
            int w = switch (code) {
                case "GC"  -> lenGC;
                case "WXP" -> lenWXP;
                case "ZH"  -> lenZH;
                case "PT"  -> lenPT;
                case "SPX" -> lenSPX;
                default    -> 0;
            };
            if (w <= 0) return;
            zones.add(new Zone(code, cur[0], w, lst,
                    "SPX".equals(code)));    // ★ new-9: SPX 必须 fromRight
            cur[0] += w;
        };

        add.accept("GC",  dfGC);
        add.accept("WXP", dfWXP);
        add.accept("ZH",  dfZH);
        add.accept("PT",  dfPT);
        add.accept("SPX", dfSPX);
        return zones;
    }

    /* ============================================================
                           规划分区 (双家公司)
       ============================================================ */
    private List<Zone> planZonesTwo(List<CargoRequest> list,
                                    int deckLen,
                                    int totalH,
                                    boolean isUpper) {
        Map<String, List<CargoRequest>> g =
                list.stream().collect(Collectors.groupingBy(CargoRequest::getCargoCode));

        if (isUpper) {
            /* 上甲板：逻辑近似单家公司，只是 SPX 不一定 right-align */
            return planZonesSingle(list, deckLen, totalH);
        }

        /* ---------------- 下甲板 (isUpper = false) ----------------
           面积比例划分；SPX 固定贴右                           */
        double totalArea = list.stream()
                .mapToDouble(c -> c.getCargoLength() * c.getCargoWidth())
                .sum();
        List<Zone> zones = new ArrayList<>();
        int cur = 0;

        /* 先普通类型，最后手动插入 SPX(贴右) */
        for (String code : List.of("GC", "WXP", "ZH", "PT")) {
            List<CargoRequest> lst =
                    "PT".equals(code)
                            ? list.stream().filter(c ->
                                    !Set.of("GC","WXP","ZH","SPX")
                                            .contains(c.getCargoCode()))
                            .collect(Collectors.toList())
                            : g.getOrDefault(code, Collections.emptyList());
            if (lst.isEmpty()) continue;

            double area = lst.stream()
                    .mapToDouble(c -> c.getCargoLength() * c.getCargoWidth())
                    .sum();
            int w = (int) Math.round(deckLen * (area / totalArea));
            // 最小保证
            w = Math.max(w, (int) (deckLen * MIN_ZONE_RATIO));
            if (cur + w > deckLen) w = deckLen - cur;
            zones.add(new Zone(code, cur, w, lst, false));
            cur += w;
        }

        /* SPX 区 */
        List<CargoRequest> dfSPX =
                g.getOrDefault("SPX", Collections.emptyList());
        if (!dfSPX.isEmpty()) {
            int spxW = calcSPXWidth(dfSPX, totalH);
            zones.add(new Zone("SPX", deckLen - spxW, spxW, dfSPX, true)); // fromRight
        }
        return zones;
    }

    /* ------------ 计算下甲板 SPX 宽度（与 Python 保持一致） ------------ */
    private int calcSPXWidth(List<CargoRequest> dfSPX, int totalH) {
        int maxH = dfSPX.stream().mapToInt(CargoRequest::getCargoWidth).max().orElse(0);
        int maxW = dfSPX.stream().mapToInt(CargoRequest::getCargoLength).max().orElse(0);
        int nSPX = dfSPX.size();
        int halfH = totalH / 2;
        int rows = (maxH > 0) ? halfH / maxH : 0;
        int cols = rows > 0 ? (int) Math.ceil(nSPX / (rows * 2.0)) : 0;
        return cols > 0 ? cols * maxW + ITEM_SPACING : maxW;
    }

    /* ============================================================
                    核心：均衡放置（支持 fromRight & spacing=0 for SPX）
       ============================================================ */
    private void balancedPack(
            List<CargoRequest> list,
            ZoneState st,
            int halfH,
            Set<Integer> placedIds
    ) {
        if (list.isEmpty()) return;

        /* 排序：GC 按长度，其余按面积，降序 */
        list.sort((a, b) -> {
            boolean aPipe = "GC".equals(a.getCargoCode());
            boolean bPipe = "GC".equals(b.getCargoCode());
            if (aPipe && bPipe) return Integer.compare(b.getCargoLength(), a.getCargoLength());
            if (aPipe) return -1;
            if (bPipe) return  1;
            int areaA = a.getCargoLength() * a.getCargoWidth();
            int areaB = b.getCargoLength() * b.getCargoWidth();
            return Integer.compare(areaB, areaA);
        });

        int usedTop = 0, usedBot = 0;

        for (CargoRequest c : list) {
            Layer first = usedBot < usedTop ? Layer.BOT : Layer.TOP;
            boolean placed = false;
            for (Layer lyr : List.of(first,
                    first == Layer.TOP ? Layer.BOT : Layer.TOP)) {
                List<PlacedCargo> tgt = lyr == Layer.TOP ? st.top : st.bot;
                if (tryPlace(c, st, halfH, lyr, tgt)) {
                    placed = true;
                    placedIds.add(c.hashCode());  // 通过 hashCode 简单去重
                    int area = c.getCargoLength() * c.getCargoWidth();
                    if (lyr == Layer.TOP) usedTop += area; else usedBot += area;
                    break;
                }
            }
            if (!placed) st.left.add(c);
        }
    }

    /* ------------ placeInZone with fromRight & SPX spacing ------------ */
    /**
     * 在指定分区尝试摆放一件货物
     *
     * @param item   货物
     * @param st     当前分区状态
     * @param zoneH  分区高度
     * @param layer  TOP / BOT
     * @param exist  已经放好的同层货物
     * @return 成功放置则返回 true
     */
    private boolean tryPlace(CargoRequest item,
                             ZoneState st,
                             int zoneH,
                             Layer layer,
                             List<PlacedCargo> exist) {

        boolean isBasket = "PT".equals(item.getCargoCode()) &&
                item.getCargoCategory().contains("吊篮");
        boolean isSPX    = "SPX".equals(item.getCargoCode());

        /* —— spacing 处理 —— */
        int checkSpacing = isSPX ? 0 : ITEM_SPACING;    // overlap 检测用，可为 0
        int step         = Math.max(1, checkSpacing);   // 扫描推进步长，必须 ≥1

        /* —— 生成尺寸候选 —— */
        List<int[]> cands = new ArrayList<>();
        if (isBasket || "GC".equals(item.getCargoCode())) {
            // 只能横放
            cands.add(new int[]{item.getCargoLength(), item.getCargoWidth()});
        } else {
            cands.add(new int[]{item.getCargoWidth(),  item.getCargoLength()});
            cands.add(new int[]{item.getCargoLength(), item.getCargoWidth()});
        }

        /* —— 扫描放置 —— */
        for (int[] wh : cands) {
            int w = wh[0], h = wh[1];

            int yStart = (layer == Layer.TOP) ? (zoneH - h) : 0;
            int yEnd   = (layer == Layer.TOP) ? -1 : (zoneH - h + 1);
            int yStep  = (layer == Layer.TOP) ? -step : step;

            for (int y = yStart; layer == Layer.TOP ? y > yEnd : y < yEnd; y += yStep) {

                if (st.fromRight) {                     // 从右往左扫
                    for (int x = st.zw - w; x >= 0; x -= step) {
                        if (!overlap(x, y, w, h, exist, checkSpacing)) {
                            exist.add(new PlacedCargo(item, x, y, w, h, layer, st.absZoneX));
                            return true;
                        }
                    }
                } else {                                // 从左往右扫
                    for (int x = 0; x <= st.zw - w; x += step) {
                        if (!overlap(x, y, w, h, exist, checkSpacing)) {
                            exist.add(new PlacedCargo(item, x, y, w, h, layer, st.absZoneX));
                            return true;
                        }
                    }
                }
            }
        }
        return false;   // 所有方向均放不下
    }


    /* ============================================================
                              全局回填
       ============================================================ */
    public void globalRefill(
            Map<String, ZoneState> zones,
            int halfH,
            Set<Integer> placedIds,
            boolean skipSPX
    ) {

        boolean progress;
        do {
            progress = false;
            List<CargoRequest> still = new ArrayList<>();
            zones.values().forEach(st -> still.addAll(st.left));

            for (CargoRequest c : still) {
                if (skipSPX && "SPX".equals(c.getCargoCode())) continue;
                boolean placed = false;
                for (ZoneState st : zones.values()) {
                    for (Layer lyr : List.of(Layer.BOT, Layer.TOP)) {
                        List<PlacedCargo> tgt = (lyr == Layer.TOP) ? st.top : st.bot;
                        if (tryPlace(c, st, halfH, lyr, tgt)) {
                            st.refillAdded.add(tgt.get(tgt.size()-1));
                            placed = progress = true;
                            placedIds.add(c.hashCode());
                            break;
                        }
                    }
                    if (placed) break;
                }
            }
        } while (progress);
    }

    /* ============================================================
                              跨公司回填
       ============================================================ */
    public void crossFill(
            List<CargoRequest> sourceCompany,
            Set<Integer> alreadyPlaced,
            int deckLen,
            int halfH,
            int baseY,
            List<ReponseResult.CargoPlanItem> out
    ) {
        List<CargoRequest> leftovers = sourceCompany.stream()
                .filter(c -> !alreadyPlaced.contains(c.hashCode()) &&
                        !"SPX".equals(c.getCargoCode()))   // 跳过 SPX
                .collect(Collectors.toList());
        if (leftovers.isEmpty()) return;

        // 简单策略：把剩余当作一个“全宽分区”从左往右塞
        ZoneState st = new ZoneState(deckLen, false);
        for (CargoRequest c : leftovers) {
            tryPlace(c, st, halfH, Layer.BOT, st.bot);
        }
        // 输出
        for (PlacedCargo pc : st.bot) {
            out.add(toPlanItem(pc, pc.x, baseY + pc.y));
        }
    }

    /* ============================================================
                        overlap with 可变 spacing
       ============================================================ */
    private boolean overlap(int x, int y, int w, int h,
                            List<PlacedCargo> exist, int spacing) {
        for (PlacedCargo e : exist) {
            if (!((x + w + spacing <= e.x) ||
                    (x >= e.x + e.w + spacing) ||
                    (y + h + spacing <= e.y) ||
                    (y >= e.y + e.h + spacing))) {
                return true;
            }
        }
        return false;
    }

    /* ============================================================
                           帮助：构造 PlanItem
       ============================================================ */
    private ReponseResult.CargoPlanItem toPlanItem(PlacedCargo p,
                                                   int absX, int absY) {
        ReponseResult.Position pos = new ReponseResult.Position();
        pos.setX(absX);
        pos.setY(absY);
        pos.setZ(0);
        // === 组装 ReponseResult.CargoPlanItem 时 ===
        String companyCode = p.src.getCompanyCode();
        int idx   = companyOrder.indexOf(companyCode);   // 0 / 1
        String colorHex = CargoColorUtils.pickColor(idx, p.src.getCargoCode());
        return new ReponseResult.CargoPlanItem(
                p.src.getCargoCategory(),
                p.src.getCargoCode(),
                p.src.getVehicleWeight(),
                p.w,
                p.h,
                colorHex,
                pos
        );
    }

    /* ============================================================
                             内部数据结构
       ============================================================ */
    private enum Layer { TOP, BOT }

    /** 增加 fromRight 标志 */
    private static class Zone {
        String code;
        int startX;
        int width;
        List<CargoRequest> cargoList;
        boolean fromRight;      // ★ new-9

        Zone(String c, int sx, int w, List<CargoRequest> list, boolean fr) {
            code = c; startX = sx; width = w; cargoList = list; fromRight = fr;
        }
    }

    /** 每个 Zone 的动态状态 */
    private static class ZoneState {
        int zw;                    // 区宽
        boolean fromRight;
        int absZoneX;              // 绝对坐标，用于 planItem
        List<PlacedCargo> top = new ArrayList<>();
        List<PlacedCargo> bot = new ArrayList<>();
        List<CargoRequest> left = new ArrayList<>();
        List<PlacedCargo> refillAdded = new ArrayList<>(); // 二次回填新增

        ZoneState(int w, boolean fr) { zw = w; fromRight = fr; absZoneX = 0; }
    }

    /** 摆放后货物信息 */
    private static class PlacedCargo {
        CargoRequest src;
        int x, y, w, h;
        Layer layer;
        int absZoneX;     // 分区全局起点 (绘制/回填用)

        PlacedCargo(CargoRequest s, int x, int y, int w, int h,
                    Layer lyr, int zoneX) {
            src = s; this.x = x; this.y = y; this.w = w; this.h = h;
            layer = lyr; absZoneX = zoneX;
        }
    }
}
